package com.gitee.feizns.dynamic.bean;

import com.gitee.feizns.dynamic.Ex;
import com.gitee.feizns.dynamic.Strings;
import com.gitee.feizns.dynamic.convert.Converter;
import com.gitee.feizns.dynamic.convert.DefaultConverter;
import com.gitee.feizns.dynamic.reflect.Classes;
import com.gitee.feizns.dynamic.reflect.Constructors;
import com.gitee.feizns.dynamic.reflect.Fields;
import com.gitee.feizns.dynamic.reflect.Methods;
import com.gitee.feizns.dynamic.reflect.Types;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <b>属性操作</b>
 * <p>
 *     该类主要辅助{@link Prop}实现
 * </p>
 * @author feizns
 * @since 2019/6/5
 */
public abstract class Props {

    /**
     * set属性方法前缀
     */
    public static final String SET_PREFIX = "set";

    /**
     * get属性方法前缀
     */
    public static final String GET_PREFIX = "get";

    /**
     * is属性方法前缀
     */
    public static final String IS_PREFIX = "is";

    /**
     * 属性前缀列表
     */
    public static final List<String> PREFIX = Arrays.asList(GET_PREFIX, SET_PREFIX, IS_PREFIX);

    /**
     * <b>获取一个类上的所有属性名</b>
     * @param type 类型
     * @return 对应的属性名称类型列表
     */
    public static <T> Stream<String> names(Class<T> type) {
        Set<String> properties = new LinkedHashSet<>();
        if ( type != null && !Types.isPrimitive(type) ) {
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                if ( isPropMethod(method) )
                    properties.add(name(method.getName()));
            }
        }
        return properties.stream();
    }

    /**
     * <b>一个类型上的所有属性</b>
     * @see #names(Class)
     * @param type 类型
     * @return 对应类型的属性列表
     */
    public static <T> Stream<Prop<Object, T>> of(Class<T> type) {
        return names(type).map(name -> Prop.of(type, name));
    }

    /**
     * <b>object上的所有的属性</b>
     * @param object 对象
     * @return 对象的属性列表
     * @throws NullPointerException object为空的情况.
     */
    @SuppressWarnings("unchecked")
    public static <T> Stream<Prop<Object, T>> of(T object) {
        return of((Class<T>) object.getClass()).map(prop -> prop.of(object));
    }

    /**
     * <b>返回一个对象上可写的属性</b>
     * @param type 任意对象
     * @return 任意对象的可写属性列表
     */
    public static <T> Stream<Prop<Object, T>> ofWritable(Class<T> type) {
        return of(type).filter(Prop::isWritable);
    }

    /**
     * <b>返回一个对象上可写的属性</b>
     * @param object 任意对象
     * @return 任意对象的可写属性列表
     */
    public static <T> Stream<Prop<Object, T>> ofWritable(T object) {
        return of(object).filter(Prop::isWritable);
    }

    /**
     * <b>返回一个类型上可读的属性</b>
     * @param type 任意对象
     * @return 任意对象的可读属性列表
     */
    public static <T> Stream<Prop<Object, T>> ofReadable(Class<T> type) {
        return of(type).filter(Prop::isReadable);
    }

    /**
     * <b>返回任意对象上可读的属性</b>
     * @param object 任意对象
     * @return 任意对象的可读属性列表
     */
    public static <T> Stream<Prop<Object, T>> ofReadable(T object) {
        return of(object).filter(Prop::isReadable);
    }

    /**
     * <b>返回类型上可读写的属性</b>
     * @param type 任意对象
     * @return 获取类型上的所有可读写的属性
     */
    public static <T> Stream<Prop<Object, T>> rw(Class<T> type) {
        return of(type).filter(prop -> prop.isReadable() && prop.isWritable());
    }

    /**
     * <b>返回任意对象上可读写的属性</b>
     * @param object 任意对象
     * @return 获取任意对象上的所有可读写的属性
     */
    public static <T> Stream<Prop<Object, T>> rw(T object) {
        return of(object).filter(prop -> prop.isReadable() && prop.isWritable());
    }

    /**
     * <b>判断是否是一个 setXxx(P p) 写入属性的方法</b>
     * @param method {@link Method}
     * @return 是否是一个写入属性的方法
     */
    public static boolean isSetMethod(Method method) {
        return method != null && method.getParameterCount() == 1
                && method.getName().startsWith(SET_PREFIX);
    }

    /**
     * <b>判断一个方法是否是一个写方法</b>
     * @param method {@link Method}
     * @return 是一个写方法返回true
     */
    public static boolean isWriteMethod(Method method) {
        return isGetMethod(method);
    }

    /**
     * <b>判断是否是一个 getXxx() 读属性的方法</b>
     * @param method {@link Method}
     * @return 是否是一个读属性的方法
     */
    public static boolean isGetMethod(Method method) {
        if ( method != null && method.getParameterCount() == 0 )
            return method.getName().startsWith(GET_PREFIX);
        return false;
    }

    /**
     * <b>返回一个方法是否是 isXxx 类型的get方法</b>
     * @param method 方法
     * @return 是boolean的isXxx的返回true
     */
    public static boolean isBooleanGetMethod(Method method) {
        if ( method != null && method.getParameterCount() == 0 )
            return method.getName().startsWith(IS_PREFIX) && method.getReturnType() == boolean.class;
        return false;
    }

    /**
     * <b>是否是一个读属性的方法</b>
     * @param method {@link Method}
     * @return 是一个 getXxx() 或是 isXxx() 的返回true
     */
    public static boolean isReadMethod(Method method) {
        return isGetMethod(method) || isBooleanGetMethod(method);
    }

    /**
     * <b>判断一个方法是否是属性方法</b>
     * @param method {@link Method}
     * @return 是一个 getXxx() 或是 isXxx() 再或者是 setXxx(P p) 的方法返回true
     */
    public static boolean isPropMethod(Method method) {
        return isWriteMethod(method) || isReadMethod(method);
    }

    /**
     * <b>将属性方法名转换为属性名：</b>
     * <pre>{@code
     *      System.out.println(PropUtils.name("getName"));//"name"
     * }</pre>
     * @return 属性名称
     */
    public static String name(String methodName) {
        for (String pre : PREFIX) {
            if ( methodName.startsWith(pre) )
                return Strings.firstLower(methodName.substring(pre.length()));
        }
        return methodName;
    }

    /**
     * <b>返回是否是 {@link Object}上的属性</b>
     * <pre>{@code
     *  System.out.println(isGetClassProp(Prop.of(Object.class, "class")));//true
     * }</pre>
     * @param prop {@link Prop}
     * @return 返回非 {@link Object}上的属性
     */
    public static <T> boolean isGetClassProp(Prop<Object, T> prop) {
        return prop.readMethod() != null && prop.readMethod().getDeclaringClass() == Object.class;
    }

    /**
     * <b>返回是否是 {@link Object}上的属性</b>
     * <pre>{@code
     *  System.out.println(isNotGetClassProp(Prop.of(Object.class, "class")));//false
     * }</pre>
     * @see #isGetClassProp(Prop)
     * @param prop {@link Prop}
     * @return 返回非 {@link Object}上的属性
     */
    public static <T> boolean isNotGetClassProp(Prop<Object, T> prop) {
        return !isGetClassProp(prop);
    }

    /**
     * <b>查找一个类型上指定属性的写方法</b>
     * <p>
     *     如果propType是int.class而type类型中没有 set对应的 set(int.class)方法<br/>
     *     则会尝试查找 set(Integer.class) 和 Integer.class能传入的方法
     * </p>
     * <pre>{@code
     *  static class A {
     *      public A setId(Object obj) { return this; }
     *  }
     *
     * public static void main(String[] args) {
     *     System.out.println(getWriteMethod(A.class, "id", int.class));//$A.setId(java.lang.Object)
     * }
     * }</pre>
     * @param type 类型
     * @param name 方法名或属性名
     * @return {@link Method}
     */
    public static Method getWriteMethod(Class<?> type, String name, Class<?> propType) {
        //获取真实的方法名
        String methodName = isStartWith(name, SET_PREFIX) ? name : SET_PREFIX + Strings.firstUpper(name);
        //找该方法
        return findWriteMethod(type, methodName, propType);
    }

    /**
     * <b>查找type类型中一个methodParameterType可以访问的方法</b>
     * @param type 查找的类型
     * @param methodName 方法名称
     * @param methodParameterType 方法参数类型
     * @return {@link Method}
     */
    private static Method findWriteMethod(Class<?> type, String methodName, Class<?> methodParameterType) {
        Method method = null;
        Class<?> wrapperType = Types.ofWrapper(methodParameterType);
        //使用基础类型查找
        if ( wrapperType != methodParameterType )
            method = Methods.find(type, methodName, methodParameterType);
        //使用包装类型查找
        if ( method == null )
            method = Methods.find(type, methodName, wrapperType);
        //使用方法参数基类查找
        if ( method == null )
            method = parameterTypeInheritanceTreeRecentMethod(wrapperType,
                    Methods.find(type, m -> isSetMethod(m) && m.getName().equals(methodName)).toArray(Method[]::new));
        return method;
    }

    /**
     * <b>查找{@code parameterType}的继承结构类型中查找{@code methods}中可以访问的方法</b>
     * @see #isWriteMethod(Method)
     * @param parameterType {@link Class}
     * @param methods {@link Method}
     * @return {@link Method}
     */
    public static Method parameterTypeInheritanceTreeRecentMethod(Class<?> parameterType, Method... methods) {
        Map<? extends Class<?>, Method> parameterTypeMapperMethod = Arrays.stream(methods).collect(Collectors.toMap(method -> method.getParameterTypes()[0], Function.identity()));
        Queue<Class<?>> inheritanceTree = Classes.ofInheritanceTree(parameterType);
        while ( !inheritanceTree.isEmpty() ) {
            Class<?> classNode = inheritanceTree.remove();
            Method method = parameterTypeMapperMethod.get(classNode);
            if ( method != null )
                return method;
        }
        return null;
    }

    /**
     * <b>在指定类型中查找一个属性的读方法</b>
     * <pre>{@code
     *     static class A {
     *         public String getName() { return "Abc"; };
     *     }
     *
     *     public static void main(String[] args) {
     *         System.out.println(getReadMethod(A.class, "name")); //$A.getName()
     *         System.out.println(getReadMethod(A.class, "getName")); //$A.getName()
     *     }
     * }</pre>
     * @param type {@link Class}
     * @param name 属性名或方法名
     * @return {@link Method}
     */
    public static Method getReadMethod(Class<?> type, String name) {
        return Ex.tryCatch(() -> {
            //查找 getXxx() 方法
            Method getMethod = Methods.find(type, GET_PREFIX + Strings.firstUpper(name));
            if ( getMethod == null ) {
                //查找 isXxx() 方法
                Method isMethod = Methods.find(type, IS_PREFIX + Strings.firstUpper(name));
                if ( isBooleanGetMethod(isMethod) )
                    return isMethod;
            }
            return getMethod;
        }).ignoreException(NoSuchFieldException.class, NoSuchMethodException.class)
                .orElse(null);
    }

    /**
     * <b>判断指定的方法是否是对应的前缀</b>
     * @param methodName 方法名称
     * @param prefix 前缀列表
     * @return methodName的前缀是prefix中的一项、则返回true
     */
    public static boolean isStartWith(String methodName, String... prefix) {
        return Arrays.stream(prefix).anyMatch(methodName::startsWith);
    }

    /**
     * <b>获取属性的字段</b>
     * @param type {@link Class}
     * @param name 属性名称
     * @return {@link Field}
     */
    public static Field getField(Class<?> type, String name) {
        return Ex.tryCatch(() -> Fields.of(type, (name))).orElse(null);
    }

    /**
     * <b>指定一个转换器、对支持的值做一次转换（如果支持转换且转换结果不为空）则设置到当前属性</b>
     * <p>
     *     将当前属性、期望从source中获取的第一个非空属性值赋值给当前属性
     * </p>
     * @param converter 转换器
     * @param values 值对象列表
     */
    @SuppressWarnings("unchecked")
    public static <D> void trySet(Prop<D, ?> prop, Converter<Object, D> converter, Object... values) {
        Class<D> writeMethodParameterType = (Class<D>) prop.writeMethodParameterType();
        for (Object value : values) {
            if ( converter.support(value, writeMethodParameterType) ) {
                D newValue = converter.to(value, writeMethodParameterType);
                if ( newValue != null )
                    prop.invokeWriteMethod(newValue);
            }
        }
    }

    /**
     * <b>获取{@link TryGet}注解</b>
     * @see TryGet
     * @param prop {@link Prop}
     * @return 优先在写函数中获取注解、否则在字段上获取
     */
    public static TryGet getTryGetAnnotation(Prop<?, ?> prop) {
        TryGet tryGet;
        tryGet = Optional.ofNullable(prop.writeMethod()).map(method -> method.getAnnotation(TryGet.class)).orElse(null);
        if ( tryGet == null )
            tryGet = Optional.ofNullable(prop.field()).map(field -> field.getAnnotation(TryGet.class)).orElse(null);
        return tryGet;
    }

    /**
     * <b>获取一个属性期望得到的属性名列表（如果没有则使用属性名）</b>
     * <p>@TryGet注解标注的属性列表</p>
     * @see TryGet
     * @return 属性期望copy到的属性名称
     */
    public static Stream<String> tryGetPropsNames(Prop<?, ?> prop) {
        TryGet tryGet  = getTryGetAnnotation(prop);
        return tryGet != null ? Arrays.stream(tryGet.value()) : Stream.of(prop.name());
    }

    /**
     * <b>传入一个对象、获取当前属性会期望从source获取的属性的值列表</b>
     * <pre>{@code
     *  @Data
     *  @Accessors(chain = true)
     *  public static class Admin {
     *      private String firstName;
     *      private String lastName;
     *  }
     *
     *  @Data
     *  public static class User {
     *      @TryGet("firstName", "lastName")
     *      private String name;
     *  }
     *
     *  public static void main(String[] args) {
     *      Admin admin = new Admin().setLastName("abc");
     *      List<Prop<Object, Object> props = Prop.of(User.class, User::getName).tryGetProps(admin).collect(Collectors.toList());
     *      List<String> names = props.stream().map(Prop::name).collect(Collectors.toList());
     *      //["firstName", "lastName"]
     *      System.out.println(names);
     *      List<Object> values = props.stream().map(Prop::val).collect(Collectors.toList());
     *      //[null, "abc"]
     *      System.out.println(values);
     *  }
     * }</pre>
     * @param source 值来源对象
     * @return 当前属性期望从source中获取的所有值
     */
    public static Stream<Prop<Object, Object>> tryGetProps(Prop<?, ?> prop, Object source) {
        return tryGetPropsNames(prop).map(name -> Prop.of(source, name));
    }

    /**
     * <b>获取{@link TryGet}标注的类型转换器</b>
     * @return 获取转换器类型
     */
    @SuppressWarnings("unchecked")
    public static <D> Converter<Object, D> tryGetConverter(Prop<D , ?> prop) {
        TryGet tryGetAnnotation = getTryGetAnnotation(prop);
        Converter<Object, D> converter = (Converter<Object, D>) DefaultConverter.DEFAULT;
        if ( tryGetAnnotation != null )
            converter = Constructors.newInstance(tryGetAnnotation.converter());
        return converter;
    }

    /**
     * <b>从source中获取property需要的属性</b>
     * @param prop {@link Prop}
     * @param source 源实例
     * @param <T> 属性数据类型
     */
    @SuppressWarnings("unchecked")
    public static <T> void tryGet(Prop<T, ?> prop, Object source) {
        Object[] values;
        //如果是Map则用属性名映射的对应的value、实体则使用属性值
        if ( source instanceof Map )  {
            Map<String, Object> sourceMap = (Map<String, Object>) source;
            values = Props.tryGetPropsNames(prop).map(sourceMap::get).toArray();
        } else
            values = Props.tryGetProps(prop, source).map(Prop::val).toArray();
        //将值设置到对应的属性中
        Props.trySet(prop, Props.tryGetConverter(prop), values);
    }

}
